package com.dc.docs._998_业务案例._9_抽奖;

import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

import java.util.*;

/**
 * Description: 抽奖算法工具类
 *
 *  算法先算出每一个奖项在所有奖项种得概率，然后用随机数，看分布在该奖项的概率区间
 *
 * @author: gdc
 * @date: 2023/9/1
 * @version 1.0
 */
public class LuckyUtil {

    /**
     * 提供一个奖品列表，会按照概率返回，列表是数组型列表
     * @param gifts         奖品列表
     * @return              奖品
     */
    public static GiftVO bingo(List<GiftVO> gifts) {
        if (CollUtil.isEmpty(gifts)) {
            return null;
        }

        // 存储概率
        List<Double> orignalRates = new ArrayList<>(gifts.size());
        for (GiftVO gift : gifts) {
            // 如果传入的
            double probability = gift.getProbability() < 0 ? 0 : gift.getProbability();
            orignalRates.add(probability);
        }
        int orignalIndex = lottery(orignalRates);
        return gifts.get(orignalIndex);
    }

    /**
     * 抽奖方法 
     * 
     * @param orignalRates  商品中奖概率列表，保证顺序和实际物品对应
     * @return              中奖商品索引
     */
    public static int lottery(List<Double> orignalRates) {
        if (CollUtil.isEmpty(orignalRates)) {
            return -1;
        }

        // 计算总概率，这样可以保证不一定总概率是1
        double sumRate = orignalRates.stream().reduce(0.0, Double::sum);

        // 计算每个物品在总概率的基础下的概率情况
        List<Double> sortOrignalRates = new ArrayList<>(orignalRates.size());
        double tempSumRate = 0d;
        for (double rate : orignalRates) {
            tempSumRate += rate;
            sortOrignalRates.add(tempSumRate / sumRate);
        }

        // 根据区块值来获取抽取到的物品索引
        double nextDouble = Math.random();
        sortOrignalRates.add(nextDouble);
        Collections.sort(sortOrignalRates);
        return sortOrignalRates.indexOf(nextDouble);
    }

    public static void main(String[] args) {
        // 构建奖品列表
        List<GiftVO> gifts = Lists.newArrayList(
                new GiftVO(1, "一等奖", "P1", 0.0001d),
                new GiftVO(2, "二等奖", "P2", 0.8d),
                new GiftVO(33, "三等级", "P32", 0.4d),
                new GiftVO(4433, "谢谢参与", "谢谢参与P3sdf2", 0.4d));

        // 测试次数
        double num = 10;

        // 统计
        Map<Integer, Integer> count = new HashMap<>();
        for (int i = 0; i < num; i++) {
            GiftVO gift = bingo(gifts);
            Integer value = count.get(gift.getId());
            count.put(gift.getId(), value == null ? 1 : value + 1);
        }
        // 输出测试结果
        for (Map.Entry<Integer, Integer> entry : count.entrySet()) {
            System.out.println(entry.getKey() + ", 命中次数=" + entry.getValue() + ", 实际概率=" + entry.getValue() / num);
        }
    }

    /**
     * Description: 奖品VO
     * @author: gdc
     * @date: 2023/9/1
     * @version 1.0
     */
    @Data
    @Builder
    @AllArgsConstructor
    public static class GiftVO {

        /**
         * 索引(奖品id,不重复就可以))
         */
        private Integer id;

        /**
         * 奖品实体（可以不传）
         */
        private Object giftObject;

        /**
         * 奖品编号（可以不传）
         */
        private String no;

        /**
         * 中奖概率（必须要）
         */
        private Double probability;
    }

}
